package com.hexingmo.dawn.lang;

import java.util.Objects;

/**
 * IP地理位置信息类
 *
 * <p>基于 <a href="http://ip-api.com/docs/api:json">ip-api.com JSON API</a> 接口返回的数据结构。</p>
 *
 * <p>该接口提供免费的IP地理位置查询服务，每分钟限制45次请求。</p>
 *
 * <p>示例JSON响应：</p>
 * <pre>{@code
 * {
 *     "status": "success",
 *     "continent": "Asia",
 *     "continentCode": "AS",
 *     "country": "China",
 *     "countryCode": "CN",
 *     "region": "SX",
 *     "regionName": "Shanxi",
 *     "city": "Liuxiang",
 *     "district": "",
 *     "zip": "",
 *     "lat": 37.8734,
 *     "lon": 112.563,
 *     "timezone": "Asia/Shanghai",
 *     "offset": 28800,
 *     "currency": "CNY",
 *     "isp": "Chinanet",
 *     "org": "Chinanet SN",
 *     "as": "AS4134 CHINANET-BACKBONE",
 *     "asname": "CHINANET-BACKBONE",
 *     "reverse": "",
 *     "mobile": false,
 *     "proxy": false,
 *     "hosting": false,
 *     "query": "219.144.252.40"
 * }
 * }</pre>
 *
 * <p>字段说明：</p>
 * <ul>
 *   <li><strong>status</strong>：查询状态（success/fail）</li>
 *   <li><strong>continent</strong>：大洲名称</li>
 *   <li><strong>continentCode</strong>：大洲代码</li>
 *   <li><strong>country</strong>：国家名称</li>
 *   <li><strong>countryCode</strong>：国家代码（ISO 3166-1 alpha-2）</li>
 *   <li><strong>region</strong>：地区/省份代码</li>
 *   <li><strong>regionName</strong>：地区/省份名称</li>
 *   <li><strong>city</strong>：城市名称</li>
 *   <li><strong>district</strong>：区县名称</li>
 *   <li><strong>zip</strong>：邮政编码</li>
 *   <li><strong>lat</strong>：纬度</li>
 *   <li><strong>lon</strong>：经度</li>
 *   <li><strong>timezone</strong>：时区标识符</li>
 *   <li><strong>offset</strong>：时区偏移量（秒）</li>
 *   <li><strong>currency</strong>：货币代码（ISO 4217）</li>
 *   <li><strong>isp</strong>：网络服务提供商</li>
 *   <li><strong>org</strong>：组织名称</li>
 *   <li><strong>as</strong>：自治系统信息</li>
 *   <li><strong>asname</strong>：自治系统名称</li>
 *   <li><strong>reverse</strong>：反向DNS查询结果</li>
 *   <li><strong>mobile</strong>：是否为移动网络</li>
 *   <li><strong>proxy</strong>：是否为代理服务器</li>
 *   <li><strong>hosting</strong>：是否为托管服务</li>
 *   <li><strong>query</strong>：查询的IP地址</li>
 *   <li><strong>message</strong>：错误信息（失败时）</li>
 * </ul>
 *
 * @author He Xing Mo
 * @since 1.0
 */
public class IPGeolocation {

    /**
     * 查询成功状态
     */
    public static final String STATUS_SUCCESS = "success";

    /**
     * 查询失败状态
     */
    public static final String STATUS_FAIL = "fail";

    /**
     * 查询状态：success 或 fail
     *
     * <p>表示API查询的结果状态。当查询成功时返回"success"，当查询失败时返回"fail"。</p>
     *
     * <p>失败的原因可能包括：</p>
     * <ul>
     *   <li>private range - 私有IP地址范围</li>
     *   <li>reserved range - 保留IP地址范围</li>
     *   <li>invalid query - 无效的查询参数</li>
     * </ul>
     */
    private String status;

    /**
     * 大洲名称
     *
     * <p>IP地址所在的大洲名称，例如：North America、Asia、Europe等。</p>
     */
    private String continent;

    /**
     * 大洲代码
     *
     * <p>大洲的两字母代码，例如：NA（北美洲）、AS（亚洲）、EU（欧洲）等。</p>
     */
    private String continentCode;

    /**
     * 国家名称
     *
     * <p>IP地址所在的国家名称，例如：United States、China、Germany等。</p>
     */
    private String country;

    /**
     * 国家代码
     *
     * <p>国家代码，遵循ISO 3166-1 alpha-2标准，例如：US（美国）、CN（中国）、DE（德国）等。</p>
     */
    private String countryCode;

    /**
     * 地区/省份代码
     *
     * <p>地区或省份的短代码，可能使用FIPS或ISO标准，例如：CA（加利福尼亚）、10（内华达）等。</p>
     */
    private String region;

    /**
     * 地区/省份名称
     *
     * <p>地区或省份的完整名称，例如：California、Nevada、Shanxi等。</p>
     */
    private String regionName;

    /**
     * 城市名称
     *
     * <p>IP地址所在的城市名称，例如：Mountain View、Beijing、Berlin等。</p>
     */
    private String city;

    /**
     * 区县名称
     *
     * <p>城市的区县或分区名称，例如：Old Farm District、海淀区等。</p>
     */
    private String district;

    /**
     * 邮政编码
     *
     * <p>该地区的邮政编码，例如：94043、100080等。</p>
     */
    private String zip;

    /**
     * 纬度
     *
     * <p>IP地址所在位置的纬度坐标，使用浮点数表示，例如：37.4192。</p>
     */
    private Double lat;

    /**
     * 经度
     *
     * <p>IP地址所在位置的经度坐标，使用浮点数表示，例如：-122.0574。</p>
     */
    private Double lon;

    /**
     * 时区标识符
     *
     * <p>该地区的时区标识符，遵循tz数据库标准，例如：America/Los_Angeles、Asia/Shanghai等。</p>
     */
    private String timezone;

    /**
     * 时区UTC DST偏移量（秒）
     *
     * <p>相对于UTC的时区偏移量，以秒为单位，考虑夏令时（DST），例如：-25200（-7小时）。</p>
     */
    private Integer offset;

    /**
     * 国家货币
     *
     * <p>该国家使用的货币代码，遵循ISO 4217标准，例如：USD（美元）、CNY（人民币）、EUR（欧元）等。</p>
     */
    private String currency;

    /**
     * ISP名称
     *
     * <p>网络服务提供商（Internet Service Provider）的名称，例如：Google、Chinanet、Deutsche Telekom等。</p>
     */
    private String isp;

    /**
     * 组织名称
     *
     * <p>拥有或管理该IP地址的组织名称，例如：Google、Chinanet SN、Deutsche Telekom AG等。</p>
     */
    private String org;

    /**
     * 自治系统编号和组织
     *
     * <p>自治系统（Autonomous System）编号和组织名称，用空格分隔，由RIR（区域互联网注册机构）分配。
     * 对于未在BGP表中宣告的IP块，此字段为空。</p>
     *
     * <p>格式示例：AS15169 Google Inc.、AS4134 CHINANET-BACKBONE等。</p>
     */
    private String as;

    /**
     * 自治系统名称
     *
     * <p>自治系统的名称，由RIR（区域互联网注册机构）分配。
     * 对于未在BGP表中宣告的IP块，此字段为空。</p>
     *
     * <p>示例：GOOGLE、CHINANET-BACKBONE等。</p>
     */
    private String asname;

    /**
     * 反向DNS查询结果
     *
     * <p>IP地址的反向DNS查询结果，例如：wi-in-f94.1e100.net。
     * 此字段可能会延迟响应，因为需要进行DNS查询。</p>
     */
    private String reverse;

    /**
     * 移动网络连接
     *
     * <p>表示是否为移动（蜂窝）网络连接，true表示是移动网络，false表示不是移动网络。</p>
     */
    private Boolean mobile;

    /**
     * 代理、VPN或Tor出口地址
     *
     * <p>表示是否为代理服务器、VPN或Tor网络的出口地址，true表示是，false表示不是。</p>
     */
    private Boolean proxy;

    /**
     * 托管、共置或数据中心
     *
     * <p>表示是否为托管服务、共置服务或数据中心的IP地址，true表示是，false表示不是。</p>
     */
    private Boolean hosting;

    /**
     * 用于查询的IP地址
     *
     * <p>用户查询的原始IP地址，例如：173.194.67.94。</p>
     */
    private String query;

    /**
     * 错误信息
     *
     * <p>当status为"fail"时包含的错误信息。可能的错误类型包括：</p>
     * <ul>
     *   <li>private range - 私有IP地址范围</li>
     *   <li>reserved range - 保留IP地址范围</li>
     *   <li>invalid query - 无效的查询参数</li>
     * </ul>
     */
    private String message;

    /**
     * 默认构造函数
     */
    public IPGeolocation() {
    }

    /**
     * 带IP地址的构造函数
     *
     * @param query 要查询的IP地址
     */
    public IPGeolocation(String query) {
        this.query = query;
    }

    // Getter 和 Setter 方法

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public String getContinent() {
        return continent;
    }

    public void setContinent(String continent) {
        this.continent = continent;
    }

    public String getContinentCode() {
        return continentCode;
    }

    public void setContinentCode(String continentCode) {
        this.continentCode = continentCode;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    public String getCountryCode() {
        return countryCode;
    }

    public void setCountryCode(String countryCode) {
        this.countryCode = countryCode;
    }

    public String getRegion() {
        return region;
    }

    public void setRegion(String region) {
        this.region = region;
    }

    public void setRegionName(String regionName) {
        this.regionName = regionName;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getDistrict() {
        return district;
    }

    public void setDistrict(String district) {
        this.district = district;
    }

    public String getZip() {
        return zip;
    }

    public void setZip(String zip) {
        this.zip = zip;
    }

    public Double getLat() {
        return lat;
    }

    public void setLat(Double lat) {
        this.lat = lat;
    }

    public Double getLon() {
        return lon;
    }

    public void setLon(Double lon) {
        this.lon = lon;
    }

    public String getTimezone() {
        return timezone;
    }

    public void setTimezone(String timezone) {
        this.timezone = timezone;
    }

    public Integer getOffset() {
        return offset;
    }

    public void setOffset(Integer offset) {
        this.offset = offset;
    }

    public String getCurrency() {
        return currency;
    }

    public void setCurrency(String currency) {
        this.currency = currency;
    }

    public String getIsp() {
        return isp;
    }

    public void setIsp(String isp) {
        this.isp = isp;
    }

    public String getOrg() {
        return org;
    }

    public void setOrg(String org) {
        this.org = org;
    }

    public String getAs() {
        return as;
    }

    public void setAs(String as) {
        this.as = as;
    }

    public String getAsname() {
        return asname;
    }

    public void setAsname(String asname) {
        this.asname = asname;
    }

    public String getReverse() {
        return reverse;
    }

    public void setReverse(String reverse) {
        this.reverse = reverse;
    }

    public Boolean getMobile() {
        return mobile;
    }

    public void setMobile(Boolean mobile) {
        this.mobile = mobile;
    }

    public Boolean getProxy() {
        return proxy;
    }

    public void setProxy(Boolean proxy) {
        this.proxy = proxy;
    }

    public Boolean getHosting() {
        return hosting;
    }

    public void setHosting(Boolean hosting) {
        this.hosting = hosting;
    }

    public String getQuery() {
        return query;
    }

    public void setQuery(String query) {
        this.query = query;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    /**
     * 检查查询是否成功
     *
     * @return true表示查询成功，false表示查询失败
     */
    public boolean isSuccess() {
        return STATUS_SUCCESS.equals(status);
    }

    /**
     * 获取完整的地理位置描述
     *
     * @return 格式化的地理位置字符串
     */
    public String getFullLocation() {
        if (!isSuccess()) {
            return "";
        }

        StringBuilder location = new StringBuilder();

        if (continent != null && !continent.isEmpty()) {
            location.append(continent);
        }

        if (country != null && !country.isEmpty()) {
            if (location.length() > 0) location.append(", ");
            location.append(country);
        }

        if (regionName != null && !regionName.isEmpty()) {
            if (location.length() > 0) location.append(", ");
            location.append(regionName);
        }

        if (city != null && !city.isEmpty()) {
            if (location.length() > 0) location.append(", ");
            location.append(city);
        }

        if (district != null && !district.isEmpty()) {
            if (location.length() > 0) location.append(", ");
            location.append(district);
        }

        return location.length() > 0 ? location.toString() : "unknown location";
    }

    /**
     * 获取坐标信息
     *
     * @return 格式化的坐标字符串
     */
    public String getCoordinates() {
        if (lat != null && lon != null) {
            return String.format("%.6f, %.6f", lat, lon);
        }
        return "坐标未知";
    }

    /**
     * 获取ASN编号
     *
     * @return ASN编号，如果无法解析则返回null
     */
    public String getAsnNumber() {
        if (as != null && as.startsWith("AS")) {
            try {
                return as.substring(2).split(" ")[0];
            } catch (Exception e) {
                // 忽略解析异常
            }
        }
        return null;
    }

    /**
     * 获取ASN名称
     *
     * @return ASN名称，如果无法解析则返回null
     */
    public String getAsnName() {
        if (as != null && as.contains(" ")) {
            try {
                return as.substring(as.indexOf(" ") + 1);
            } catch (Exception e) {
                // 忽略解析异常
            }
        }
        return asname;
    }

    /**
     * 获取时区偏移量（小时）
     *
     * @return 时区偏移量（小时），如果无法获取则返回null
     */
    public Integer getOffsetHours() {
        if (offset != null) {
            return offset / 3600;
        }
        return null;
    }

    /**
     * 检查是否为移动网络
     *
     * @return true表示是移动网络，false表示不是移动网络
     */
    public boolean isMobileNetwork() {
        return Boolean.TRUE.equals(mobile);
    }

    /**
     * 检查是否为代理服务器
     *
     * @return true表示是代理服务器，false表示不是代理服务器
     */
    public boolean isProxyServer() {
        return Boolean.TRUE.equals(proxy);
    }

    /**
     * 检查是否为托管服务
     *
     * @return true表示是托管服务，false表示不是托管服务
     */
    public boolean isHostingService() {
        return Boolean.TRUE.equals(hosting);
    }

    @Override
    public String toString() {
        return "IPGeolocation{" +
                "status='" + status + '\'' +
                ", continent='" + continent + '\'' +
                ", continentCode='" + continentCode + '\'' +
                ", country='" + country + '\'' +
                ", countryCode='" + countryCode + '\'' +
                ", region='" + region + '\'' +
                ", regionName='" + regionName + '\'' +
                ", city='" + city + '\'' +
                ", district='" + district + '\'' +
                ", zip='" + zip + '\'' +
                ", lat=" + lat +
                ", lon=" + lon +
                ", timezone='" + timezone + '\'' +
                ", offset=" + offset +
                ", currency='" + currency + '\'' +
                ", isp='" + isp + '\'' +
                ", org='" + org + '\'' +
                ", as='" + as + '\'' +
                ", asname='" + asname + '\'' +
                ", reverse='" + reverse + '\'' +
                ", mobile=" + mobile +
                ", proxy=" + proxy +
                ", hosting=" + hosting +
                ", query='" + query + '\'' +
                ", message='" + message + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        IPGeolocation that = (IPGeolocation) o;
        return Objects.equals(status, that.status) &&
                Objects.equals(continent, that.continent) &&
                Objects.equals(continentCode, that.continentCode) &&
                Objects.equals(country, that.country) &&
                Objects.equals(countryCode, that.countryCode) &&
                Objects.equals(continent, that.continent) &&
                Objects.equals(regionName, that.regionName) &&
                Objects.equals(city, that.city) &&
                Objects.equals(district, that.district) &&
                Objects.equals(zip, that.zip) &&
                Objects.equals(lat, that.lat) &&
                Objects.equals(lon, that.lon) &&
                Objects.equals(timezone, that.timezone) &&
                Objects.equals(offset, that.offset) &&
                Objects.equals(currency, that.currency) &&
                Objects.equals(isp, that.isp) &&
                Objects.equals(org, that.org) &&
                Objects.equals(as, that.as) &&
                Objects.equals(asname, that.asname) &&
                Objects.equals(reverse, that.reverse) &&
                Objects.equals(mobile, that.mobile) &&
                Objects.equals(proxy, that.proxy) &&
                Objects.equals(hosting, that.hosting) &&
                Objects.equals(query, that.query) &&
                Objects.equals(message, that.message);
    }

    @Override
    public int hashCode() {
        return Objects.hash(status, continent, continentCode, country, countryCode, region, regionName, city, district, zip,
                lat, lon, timezone, offset, currency, isp, org, as, asname, reverse, mobile, proxy, hosting, query, message);
    }
}